#ifndef PORTABLE_SERIALIZER_H_
#define PORTABLE_SERIALIZER_H_
#include <fstream>
#include <vector>

#include "src/pbc/portable-constant-array-builder.h"
#include "src/pbc/portable-format.h"
#include "src/pbc/portable-function.h"
#include "src/pbc/portable-literals.h"
#include "src/pbc/portable-scopes.h"

namespace portable {
class PbcWriter {
 public:
  uint32_t Position() const {
    P_CHECK(buffer_.size() <= static_cast<size_t>(UINT32_MAX));
    return static_cast<uint32_t>(buffer_.size());
  }

  const std::vector<uint8_t>& Buffer() const { return buffer_; }

  void WriteByte(uint8_t byte) { buffer_.push_back(byte); }

  void WriteBytes(const uint8_t* start, size_t size) {
    buffer_.insert(buffer_.end(), start, start + size);
  }

  void WriteInt16(uint16_t value) {
    WriteBytes(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
  }

  void WriteInt32(uint32_t value) {
    WriteBytes(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
  }

  void WriteInt64(uint64_t value) {
    WriteBytes(reinterpret_cast<const uint8_t*>(&value), sizeof(value));
  }

  void WriteString(const std::string& str) {
    WriteBytes(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
  }

  void WriteU16String(const std::u16string& str) {
    WriteBytes(reinterpret_cast<const uint8_t*>(str.c_str()),
               str.size() * sizeof(char16_t));
  }

  void WritePaddings(uint32_t number, const char* hint = nullptr) {
    (void)hint;
    for (uint32_t i = 0; i < number; ++i) {
      WriteByte(0);
    }
  }

  void WriteConstantEntry(const PbcValue& entry) {
    WriteBytes(reinterpret_cast<const uint8_t*>(&entry), PbcRef::kSize);
  }

  void WriteShortConstantEntry(const PbcShortValue& entry) {
    WriteBytes(reinterpret_cast<const uint8_t*>(&entry), PbcShortValue::kSize);
  }

 private:
  std::vector<uint8_t> buffer_;
};

class PbcSerializer {
 public:
  explicit PbcSerializer(std::vector<Function*>& func_list)
      : func_list_(func_list) {
    script_writer_ = new PbcWriter();
    func_headers_.resize(func_list_.size());
    func_writers_.reserve(func_list_.size());
    for (size_t i = 0; i < func_list_.size(); ++i) {
      func_writers_.push_back(new PbcWriter());
    }
  }

  ~PbcSerializer();

  void Run();
#if 0
  void Output(std::ofstream& os);
#endif
  void Output(std::vector<uint8_t>& buffer);

 private:
  void SetCurrentFuncWriter(uint32_t func_id) {
    P_CHECK(func_id < func_writers_.size());
    current_func_writer_ = func_writers_[func_id];
  }

  uint32_t AddPaddings(uint32_t alignment, PbcWriter& writer) {
    uint32_t pos = writer.Position();
    uint32_t mod = pos % alignment;
    if (mod == 0) {
      return 0;
    }
    uint32_t num_padding_bytes = alignment - mod;
    for (uint32_t i = 0; i < num_padding_bytes; ++i) {
      writer.WritePaddings(num_padding_bytes);
    }
    return num_padding_bytes;
  }

  // Script level
  template <typename T>
  void OptimiseSubstrInStringTable(std::vector<T>& strings);
  void OptimiseStringTable();
  uint32_t SerializeTopDeclarations();
  uint32_t SerializeDoubleTable();
  uint32_t SerializeStringStorage();
  uint32_t SerializeU16StringStorage();
  uint32_t SerializeBigIntStorage();
  uint32_t SerializeLiteralStorage();
  uint32_t SerializeScopeStorage();
  uint32_t SerializeFunctionHeaderTable();

  // Function level
  uint32_t SerializeFunction(Function& func, uint32_t func_id);
  uint32_t SerializeBytecode(const BytecodeArray& bytecode);
  uint32_t SerializeShortConstantTable(const ConstantPool& constant_pool);
  uint32_t SerializeConstantTable(const ConstantPool& constant_pool);
  uint32_t SerializeFeedbackMeta(const FeedbackVectorSpec& feedback_spec);

  BigIntRef GetOrCreateBigInt(const PortableBigInt& bigint) {
    return GetOrCreateBigInt(bigint.str());
  }

  BigIntRef GetOrCreateBigInt(const std::string& bigint) {
    // TODO:(wwq) unique optimization
    auto it = bigint_map_.find(&bigint);
    if (it != bigint_map_.end()) {
      return it->second;
    }
    bigint_table_.push_back(&bigint);
    P_CHECK(bigint.size() <= static_cast<size_t>(UINT32_MAX));
    uint32_t bigint_size = static_cast<uint32_t>(bigint.size());
    BigIntRef item(PbcValueKind::kBigInt, bigint_cursor_, bigint_size);
    bigint_cursor_ += bigint_size;
    bigint_map_.emplace(&bigint, item);
    return item;
  }

  StringRef GetOrCreateU8String(const std::string& str) {
    auto it = string_map_.find(str);
    if (it != string_map_.end()) {
      return it->second;
    }
    string_table_.push_back(str);
    P_CHECK(str.size() <= static_cast<size_t>(UINT32_MAX));
    StringRef item(PbcValueKind::kU8String, string_cursor_,
                   static_cast<uint32_t>(str.size()));
    string_cursor_ += str.size();
    string_map_.emplace(str, item);
    return item;
  }

  StringRef GetOrCreateU16String(const std::u16string& str) {
    auto it = u16string_map_.find(str);
    if (it != u16string_map_.end()) {
      return it->second;
    }
    u16string_table_.push_back(str);
    P_CHECK(str.size() <= static_cast<size_t>(UINT32_MAX));
    StringRef item(PbcValueKind::kU16String, u16string_cursor_,
                   static_cast<uint32_t>(str.size()));
    u16string_cursor_ += (str.size() * sizeof(char16_t));
    u16string_map_.emplace(str, item);
    return item;
  }

  StringRef GetOrCreateString(String str) {
    if (std::holds_alternative<std::string>(str)) {
      return GetOrCreateU8String(std::get<std::string>(str));
    } else {
      return GetOrCreateU16String(std::get<std::u16string>(str));
    }
  }

  DoubleRef GetOrCreateDouble(double value) {
    P_CHECK(double_table_.size() <= static_cast<size_t>(UINT32_MAX));
    uint32_t id = static_cast<uint32_t>(double_table_.size());
    double_table_.push_back(value);
    DoubleRef item(id);
    return item;
  }

  PbcValue ToPbcValue(const LiteralElement& elem);
  bool CheckToPbcShortValue(const LiteralElement& elem);
  bool CheckToShortConstantTable(const ConstantPool& constant_pool);
  PbcShortValue ToPbcShortValue(const LiteralElement& elem);
  LiteralRef GetOrCreateArrayLiteral(const LiteralElement& array);
  LiteralRef GetOrCreateObjectLiteral(const LiteralElement& object);
  LiteralRef GetOrCreateTemplateObjectLiteral(
      const TemplateObjectDescription& object);
  LiteralRef GetOrCreateClassLiteral(const LiteralElement& classLiteral);
  ScopeRef GetOrCreateScope(const Scope& scope);
  ScopeRef GetCreatedScope(const Scope& scope);

  PbcHeader pbc_header_;
  std::vector<Function*>& func_list_;
  std::vector<FunctionHeader> func_headers_;
  std::vector<PbcWriter*> func_writers_;

  PbcWriter* current_func_writer_ = nullptr;

  PbcWriter* script_writer_ = nullptr;

  std::vector<StringRef> top_declarations_;

  std::vector<double> double_table_;

  std::vector<std::string> string_table_;
  uint32_t string_cursor_ = 0;
  std::unordered_map<std::string, StringRef> string_map_;

  std::vector<std::u16string> u16string_table_;
  uint32_t u16string_cursor_ = 0;
  std::unordered_map<std::u16string, StringRef> u16string_map_;

  std::vector<const std::string*> bigint_table_;
  uint32_t bigint_cursor_ = 0;
  std::unordered_map<const std::string*, BigIntRef> bigint_map_;

  // first: raw literal pointer, second: raw literal size in byte
  std::vector<std::pair<uint8_t*, uint32_t>> literal_table_;
  uint32_t literal_cursor_ = 0;

  std::vector<std::vector<LiteralElement>> array_literal_map_;
  std::vector<LiteralRef> array_literal_ref_;
  std::vector<std::vector<LiteralElement>> object_literal_map_;
  std::vector<LiteralRef> object_literal_ref_;

  // // first: LocalVar pointer, second: LocalVar size in byte
  // std::vector<std::pair<uint8_t*, uint32_t>> scope_local_var_table_;
  std::vector<std::vector<uint8_t>> local_var_table_;
  std::vector<RawScope*> scope_table_;
  uint32_t local_cursor_ = 0;

  std::unordered_map<const Scope*, std::pair<RawScope*, ScopeRef>> scope_map_;

  uint32_t bytecode_storage_cursor_ = 0;
};
}  // namespace portable

#endif  // PORTABLE_SERIALIZER_H_
